package cz.zcu.fav.rat.rest.controller;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

import cz.zcu.fav.rat.beans.Object;
import cz.zcu.fav.rat.beans.Period;
import cz.zcu.fav.rat.beans.Reservation;
import cz.zcu.fav.rat.beans.User;
import cz.zcu.fav.rat.dataaccess.service.ObjectService;
import cz.zcu.fav.rat.dataaccess.service.PeriodService;
import cz.zcu.fav.rat.dataaccess.service.ReservationService;
import cz.zcu.fav.rat.dataaccess.service.TimeSlot;
import cz.zcu.fav.rat.dataaccess.service.UserService;
import cz.zcu.fav.rat.dataaccess.service.impl.OccupiedReservationTimeSlotException;
import cz.zcu.fav.rat.rest.beans.OccupationMapping;
import cz.zcu.fav.rat.rest.beans.OccupationsMapping;
import cz.zcu.fav.rat.rest.beans.ReservationsMapping;
import cz.zcu.fav.rat.rest.errorhandling.UnauthorizedAccessRestException;
import cz.zcu.fav.rat.rest.errorhandling.UnknownResourceException;
import cz.zcu.fav.rat.rest.factory.ObjectsMappingFactory;
import cz.zcu.fav.rat.rest.factory.PeriodMappingFactory;
import cz.zcu.fav.rat.rest.factory.PropertiesMappingFactory;
import cz.zcu.fav.rat.rest.factory.ReservationsMappingFactory;
import cz.zcu.fav.rat.rest.factory.UserMappingFactory;
import cz.zcu.fav.rat.rest.utils.RestUtils;
import cz.zcu.fav.rat.security.beans.UserDetailsBean;
import cz.zcu.fav.rat.security.beans.UserSessionController;

@Controller
@RequestMapping("/rest/users")
public class UserRestController
{

	@Autowired
	PeriodService periodService;

	@Autowired
	UserService userService;

	@Autowired
	ObjectService objectService;

	@Autowired
	PropertiesMappingFactory propertiesMappingFactory;

	@Autowired
	ReservationService reservationService;

	@Autowired
	UserMappingFactory userMappingFactory;

	@Autowired
	PeriodMappingFactory periodMappingFactory;

	@Autowired
	ReservationsMappingFactory reservationsMappingFactory;

	@Autowired
	ObjectsMappingFactory objectsMappingFactory;

	@Autowired
	Jaxb2Marshaller jaxb2Marshaller;
	String XML_VIEW_NAME = "userRestView";

	@RequestMapping(method = RequestMethod.GET, value = "/user/name")
	public ModelAndView getUser(
			@RequestParam(value = "name", required = true) String name)
	{
		// TODO: zmenit tu url na neco inteligentnejsiho
		List<User> users = userService.findByNickname(name);
		if (users.isEmpty())
		{
			throw new UnknownResourceException(
					"No user with this name was found!");
		}
		User user = users.iterator().next();
		return new ModelAndView(XML_VIEW_NAME, "user",
				userMappingFactory.getMappedObject(user));
	}

	@RequestMapping(method = RequestMethod.GET, value = "/user")
	public ModelAndView getUserById(
			@RequestParam(value = "id", required = true) Integer id)
	{
		User user = userService.get(id);
		if (user == null)
		{
			throw new UnknownResourceException(
					"No user with this id was found!");
		}
		return new ModelAndView(XML_VIEW_NAME, "user",
				userMappingFactory.getMappedObject(user));
	}

	@RequestMapping(method = RequestMethod.GET, value = "/user/favourites")
	public ModelAndView getUserFavourites()
	{
		User user = getLoggedUser();
		Set<Object> favourites = user.getFavourites();
		return new ModelAndView(XML_VIEW_NAME, "user",
				objectsMappingFactory.getMappedObject(favourites));
	}

	@RequestMapping(method = RequestMethod.GET, value = "/session/properties")
	public ModelAndView getSystemProperties()
	{
		return new ModelAndView(XML_VIEW_NAME, "user",
				propertiesMappingFactory.getPropertiesMapping());
	}

	@RequestMapping(method = RequestMethod.GET, value = "/session/reservations/occupation")
	public ModelAndView getUserReservationsInMonth(
			@RequestParam(value = "month", required = true) Integer month,
			@RequestParam(value = "year", required = true) Integer year)
	{
		User user = getLoggedUser();

		LocalDate dt = new LocalDate(year, month, 1);
		LocalDate since = dt.dayOfMonth().withMinimumValue();
		LocalDate to = dt.dayOfMonth().withMaximumValue();

		Map<LocalDate, Collection<LocalDate>> dates = periodService.getOccupiedDaysByUser(
		user.getId(), since, to);
		
		OccupationsMapping mapping = new OccupationsMapping();

		
		for(Entry<LocalDate, Collection<LocalDate>> entry : dates.entrySet()) 
		{	
			OccupationMapping dateMapping = new OccupationMapping();
			dateMapping.setCount(entry.getValue().size());
			dateMapping.setDate(entry.getKey().toString());
			mapping.addDate(dateMapping);
		}
		
		return new ModelAndView(XML_VIEW_NAME, "user", mapping);
	}

	@RequestMapping(method = RequestMethod.GET, value = "/session/reservations/notreturned")
	public ModelAndView getUserNotReturnedObjects()
	{
		User user = getLoggedUser();
		Multimap<Reservation, Period> periods = periodService
				.getPeriodsByUserIdNotReturned(user.getId());

		return new ModelAndView(XML_VIEW_NAME, "user",
				reservationsMappingFactory.getMappedObject(periods.asMap()));
	}

	@RequestMapping(method = RequestMethod.POST, value = "/session/reservations/return")
	public ModelAndView returnObject(
			@RequestParam(value = "id", required = true) Integer id)
	{
		User user = getLoggedUser();
		Multimap<Reservation, Period> map = periodService.returnPeriod(
				user.getId(), id);

		if (map == null)
		{
			throw new UnauthorizedAccessRestException(
					"This period does not belong to your reservation!");
		}
		return new ModelAndView(XML_VIEW_NAME, "user",
				reservationsMappingFactory.getMappedObject(map.asMap()));
	}

	@RequestMapping(method = RequestMethod.POST, value = "/session/favourite")
	public ModelAndView addFavourite(
			@RequestParam(value = "id", required = true) Integer id)
	{
		User user = getLoggedUser();

		Object obj = objectService.get(id);
		userService.addNewFavouriteToUser(user, obj);
		Set<Object> objSet = new HashSet<Object>();
		objSet.add(obj);

		return new ModelAndView(XML_VIEW_NAME, "user",
				objectsMappingFactory.getMappedObject(objSet));
	}

	@RequestMapping(method = RequestMethod.DELETE, value = "/session/favourite")
	public ModelAndView removeFavourite(
			@RequestParam(value = "id", required = true) Integer id)
	{
		User user = getLoggedUser();
		Object obj = objectService.get(id);

		// Set<Object> favourites = user.getFavourites();
		userService.removeFavouriteToUser(user, obj);
		Set<Object> objSet = new HashSet<Object>();
		objSet.add(obj);

		return new ModelAndView(XML_VIEW_NAME, "user",
				objectsMappingFactory.getMappedObject(objSet));
	}

	@RequestMapping(method = RequestMethod.DELETE, value = "/session/reservation")
	public ModelAndView removeReservation(
			@RequestParam(value = "id", required = true) Integer id)
	{
		User user = getLoggedUser();
		Reservation reservation = reservationService.get(id);

		if (reservation == null)
		{
			throw new UnknownResourceException("Reservation with id " + id
					+ "does not exist");
		}
		if (!reservation.getUser().equals(user))
		{
			throw new UnauthorizedAccessRestException(
					"This is not your reservation!");
		}

		Map<Reservation, Collection<Period>> map = new HashMap<Reservation, Collection<Period>>();
		// List<Period> periods = new ArrayList<Period>();
		// periods.addAll(reservation.getPeriods());
		// map.put(reservation, periods);
		// Cascade delete is handled by hibernate
		reservationService.delete(reservation);

		return new ModelAndView(XML_VIEW_NAME, "user",
				reservationsMappingFactory.getMappedObject(map));
	}

	@RequestMapping(method = RequestMethod.GET, value = "/session/reservations")
	public ModelAndView getUserReservations(
			@RequestParam(value = "since", required = true) String since,
			@RequestParam(value = "to", required = false) String to)
			throws UnknownResourceException
	{
		User user = getLoggedUser();
		LocalDateTime fromDate = RestUtils.convertRestTimeToJodaTime(since);

		LocalDateTime toDate = RestUtils.convertRestTimeToJodaTime(to);

		List<Period> periods = periodService.getAllPeriodsByUserIdInDateRange(
				user.getId(), fromDate, toDate);

		Multimap<Reservation, Period> multiMap = ArrayListMultimap.create();
		for (Period period : periods)
		{
			Reservation res = period.getReservation();
			multiMap.put(res, period);
		}

		Map<Reservation, Collection<Period>> reservationMap = multiMap.asMap();

		return new ModelAndView(XML_VIEW_NAME, "user",
				reservationsMappingFactory.getMappedObject(reservationMap));
	}

	@RequestMapping(method = RequestMethod.PUT, value = "/session/reservation")
	public ModelAndView addReservation(@RequestBody String body)
	{
		User user = getLoggedUser();

		Source source = new StreamSource(new StringReader(body));
		ReservationsMapping resMapping = (ReservationsMapping) jaxb2Marshaller
				.unmarshal(source);

		Map<Reservation, Collection<Period>> remapped = reservationsMappingFactory
				.getRemappedObject(resMapping, true);
		Map<Reservation, Collection<Period>> returnedRes = new HashMap<Reservation, Collection<Period>>();

		for (Reservation res : remapped.keySet())
		{
			res.setUser(user);
		}

		try
		{
			reservationService.createReservations(remapped.keySet());
		} catch (OccupiedReservationTimeSlotException ex)
		{
			Period period = periodService.get(ex.getPeriodId());
			List<Period> perList = new ArrayList<Period>();
			perList.add(period);
			returnedRes.put(period.getReservation(), perList);
		}

		return new ModelAndView(XML_VIEW_NAME, "user",
				reservationsMappingFactory.getMappedObject(returnedRes));
	}

	// @RequestMapping(method = RequestMethod.POST, value =
	// "/session/reservation/period")
	// public ModelAndView modifyPeriod(@RequestBody String body)
	// {
	// Source source = new StreamSource(new StringReader(body));
	// PeriodMapping perMapping = (PeriodMapping) jaxb2Marshaller
	// .unmarshal(source);
	// Period postedPeriod = periodMappingFactory.getRemappedObject(perMapping,
	// false);
	//
	//
	// Integer userId = UserSessionController.getLoggedUserBean().getId();
	// Period period = periodService.get(period.getId());
	// if (period == null)
	// {
	// throw new UnknownResourceException("Period with id " + id
	// + "does not exist");
	// }
	//
	// if (!period.getReservation().getUser().getId().equals(userId))
	// {
	// throw new UnauthorizedAccessRestException(
	// "This period does not belong to your reservation!");
	// }
	//
	// // lets set up periods validity first
	// if (valid != null)
	// {
	// period.setValid(valid);
	// periodService.update(period);
	// }
	//
	// Map<Reservation, Collection<Period>> returnedRes = new
	// HashMap<Reservation, Collection<Period>>();
	//
	// if (since != null && to != null)
	// {
	// LocalDateTime sinceDateTime = RestUtils
	// .convertRestTimeToJodaTime(since);
	// LocalDateTime toDateTime = RestUtils.convertRestTimeToJodaTime(to);
	//
	// try
	// {
	// TimeSlot timeslot = new TimeSlot(sinceDateTime, toDateTime);
	//
	// reservationService.mergeMultiple(period, timeslot);
	// } catch (OccupiedReservationTimeSlotException ex)
	// {
	// Period duplPeriod = periodService.get(ex.getPeriodId());
	// List<Period> perList = new ArrayList<Period>();
	// perList.add(duplPeriod);
	// returnedRes.put(duplPeriod.getReservation(), perList);
	// }
	// }
	//
	// return new ModelAndView(XML_VIEW_NAME, "user",
	// reservationsMappingFactory.getMappedObject(returnedRes));
	// }

	@RequestMapping(method = RequestMethod.POST, value = "/session/reservation/period")
	public ModelAndView modifyPeriod(
			@RequestParam(value = "id", required = true) Integer id,
			@RequestParam(value = "since", required = false) String since,
			@RequestParam(value = "to", required = false) String to,
			@RequestParam(value = "valid", required = false) Boolean valid)
	{
		Integer userId = UserSessionController.getLoggedUserBean().getId();
		Period period = periodService.get(id);
		if (period == null)
		{
			throw new UnknownResourceException("Period with id " + id
					+ "does not exist");
		}

		if (!period.getReservation().getUser().getId().equals(userId))
		{
			throw new UnauthorizedAccessRestException(
					"This period does not belong to your reservation!");
		}

		// lets set up periods validity first
		if (valid != null)
		{
			period = periodService.setValid(period, valid);
		}

		Map<Reservation, Collection<Period>> returnedRes = new HashMap<Reservation, Collection<Period>>();

		if (since != null && to != null)
		{
			LocalDateTime sinceDateTime = RestUtils
					.convertRestTimeToJodaTime(since);
			LocalDateTime toDateTime = RestUtils.convertRestTimeToJodaTime(to);
			if (!sinceDateTime.isEqual(period.getFromDate())
					|| !toDateTime.isEqual(period.getToDate()))
			{
				try
				{
					TimeSlot timeslot = new TimeSlot(sinceDateTime, toDateTime);

					reservationService.mergeMultiple(period,
							timeslot);
				} catch (OccupiedReservationTimeSlotException ex)
				{
					Period duplPeriod = periodService.get(ex.getPeriodId());
					List<Period> perList = new ArrayList<Period>();
					perList.add(duplPeriod);
					returnedRes.put(duplPeriod.getReservation(), perList);
				}
			}
		}

		return new ModelAndView(XML_VIEW_NAME, "user",
				reservationsMappingFactory.getMappedObject(returnedRes));
	}

	private User getLoggedUser() throws UnauthorizedAccessRestException
	{

		UserDetailsBean userBean = UserSessionController.getLoggedUserBean();
		Integer userId = userBean.getId();

		User user = userService.get(userId);
		if (user == null)
		{
			throw new UnknownResourceException("User with id "
					+ userBean.getUsername() + " was not found");
		}
		return user;
	}
}
